/*
 Xern Entity Relocation Network
 Copyright (C) 2010-2011 Ronie Salgado <roniesalg@gmail.com>

 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version 2.1 of the License, or (at your option) any later version.

 This library is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public
 License along with this library; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
*/

#include <stdio.h>
#include <sstream>
#include <OgreCamera.h>
#include <OgreException.h>
#include <OgreViewport.h>
#include <OgreSceneManager.h>
#include <OgreRenderWindow.h>
#include <OgreDefaultHardwareBufferManager.h>
#include "OgreRenderer.hpp"

#include "Xern/Engine.hpp"
#include "Xern/Exceptions.hpp"
#include "Xern/IGuiSystem.hpp"
#include "Xern/IInputSystem.hpp"
#include "Xern/PluginRegistrator.hpp"
#include "Xern/ResourceManager.hpp"
#include "Xern/RegionMesh.hpp"

#include "Camera.hpp"
#include "Geometry.hpp"
#include "GeometryInstance.hpp"
#include "Light.hpp"
#include "Material.hpp"
#include "OgreLoader.hpp"
#include "ParticleSystem.hpp"
#include "Primitive.hpp"
#include "SceneNode.hpp"
#include "ShadowListener.hpp"
#include "Texture.hpp"

namespace XernOgre
{
	static PluginRegistrator<OgreRenderer>
	ogreRenderer("Renderer", "OgreRenderer");

	OgreRenderer::OgreRenderer()
	{
		root = NULL;
		shadowListener = NULL;
		myGuiOgre = NULL;
		meshCounter = 0;
		materialCounter = 0;
		textureCounter = 0;
		systemCounter = 0;
		entityCounter = 0;
		rendering = false;
	}

	OgreRenderer::~OgreRenderer()
	{
		if(root)
			Shutdown();
	}

	void OgreRenderer::Initialize()
	{
#ifdef _DEBUG
		pluginsConfig = "cfg/plugins_d.cfg";
#else
		pluginsConfig = "cfg/plugins.cfg";
#endif

		// Store the use render flag.
		rendering = Engine::Get().GetSystemDescription().useRendering;

		// Construct the Ogre root object.
		root = new Ogre::Root(pluginsConfig);

		// Setup the resources.
		SetupResources();

		// Create the custom ogre resource loader.
		ogreLoader.reset(new OgreLoader(shared_from_this()));
		Engine::Get().GetResourceManager()->RegisterResourceLoader(ogreLoader);

		// Set the video mode.
		if(rendering)
		{
			SetVideoMode();

			// Set default mipmap level (NB some APIs ignore this)
			Ogre::TextureManager::getSingleton().setDefaultNumMipmaps(5);

			// Register as a Window listener
			Ogre::WindowEventUtilities::addWindowEventListener(renderWindow, this);
		}
		else
		{
			CreateNullRenderer();
		}

		// Initialize all resource groups
		Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();

		// Create the scene manager.
		sceneManager = root->createSceneManager("DefaultSceneManager");

		if(rendering)
		{
			// Setup lighting.
			SetupLighting();

			// Setup the camera.
			SetupCamera();

			// Register a frame listener.
			root->addFrameListener(this);

			// Get the MyGUISystem
			boost::shared_ptr<IGuiSystem> guiSys =
					Engine::Get().GetPluginRegistry()->QueryInterface<IGuiSystem> ("GUI", "MyGUI");
			if(guiSys)
			{
				// Create the gui platform
				myGuiOgre = new MyGUI::OgrePlatform();
				myGuiOgre->initialise(renderWindow, sceneManager, "Gui");
				guiSys->SetDriver(myGuiOgre);
			}
		}
	}

	void OgreRenderer::CreateNullRenderer()
	{
		// Select the first render system.
		Ogre::RenderSystemList::const_iterator rit = root->getAvailableRenderers().begin();
		root->setRenderSystem(*rit);

		// Initialise the root without creating a window.
		root->initialise(false);

		// Create the render window.
		renderWindow = root->createRenderWindow("dummy", 1, 1, false);

		// Destroy it, now.
		renderWindow->destroy();
		renderWindow = NULL;
	}

	void OgreRenderer::SetVideoMode()
	{
		// Configure the video mode.
		if(!root->restoreConfig())
		{
			if(!root->showConfigDialog())
				throw XernException("Failed to setup the video mode.");
		}

		// Initialize the render system.
		renderWindow = root->initialise(true, "Xern");

		// Configure the input system.
		InputParameters inputParams;
		size_t windowHandler = 0;
		std::ostringstream windowHandlerStr;

		renderWindow->getCustomAttribute("WINDOW", &windowHandler);
		windowHandlerStr << windowHandler;
		inputParams.insert(std::make_pair("WINDOW", windowHandlerStr.str()));
		Engine::Get().GetInputSystem()->SetParameters(inputParams);
		windowResized(renderWindow);
	}

	void OgreRenderer::SetupLighting()
	{
		// Set the ambient light.
		sceneManager->setAmbientLight(Ogre::ColourValue(0.2, 0.2, 0.2));

		// Set shadowing.
		//Ogre::LiSPSMShadowCameraSetup *lispsmSetup = new Ogre::LiSPSMShadowCameraSetup();

		Ogre::ShadowCameraSetupPtr shadowSetup(new Ogre::DefaultShadowCameraSetup() /*lispsmSetup*/);
		shadowListener = new ShadowListener(sceneManager, shadowSetup);
		sceneManager->addListener(shadowListener);
		sceneManager->setShadowTextureCount(3);
		sceneManager->setShadowTextureCountPerLightType(Ogre::Light::LT_DIRECTIONAL, 3);
		sceneManager->setShadowTextureConfig(0, 512, 512, Ogre::PF_FLOAT32_R);
		sceneManager->setShadowTextureConfig(1, 512, 512, Ogre::PF_FLOAT32_R);
		sceneManager->setShadowTextureConfig(2, 512, 512, Ogre::PF_FLOAT32_R);

		sceneManager->setShadowTechnique(Ogre::SHADOWTYPE_TEXTURE_ADDITIVE_INTEGRATED);
		sceneManager->setShadowTextureFadeStart(1.f);
		sceneManager->setShadowTextureFadeEnd(1.f);
		sceneManager->setShadowTextureSelfShadow(true);
		sceneManager->setShadowFarDistance(300.0);
		sceneManager->setShadowTextureCasterMaterial("ShadowCaster");

		//sceneManager->setShadowCameraSetup(shadowSetup);
		sceneManager->setShadowCasterRenderBackFaces(false);

		// Setup shadow texture filtering.
		for(size_t i = 0; i < 3; i++)
		{
			Ogre::TexturePtr shadowTex = sceneManager->getShadowTexture(i);
			Ogre::RenderTarget *rt = shadowTex->getBuffer()->getRenderTarget();
			Ogre::Viewport *port = rt->getViewport(0);

			// Add the shadow compositor.
			Ogre::CompositorManager::getSingleton().addCompositor(port, "Shadow/Filter")->setEnabled(true);
		}
	}

	void OgreRenderer::SetupCamera()
	{
		// Create the camera.
		globalCamera = sceneManager->createCamera("GlobalCamera");
		globalCamera->setPosition(Ogre::Vector3(0, 0, 10));
		globalCamera->lookAt(Ogre::Vector3(0,0,-300));
		globalCamera->setNearClipDistance(0.1);
		globalCamera->setFarClipDistance(120.0);

		// Create a viewport.
		viewport = renderWindow->addViewport(globalCamera);
		viewport->setBackgroundColour(Ogre::ColourValue(0,0,0));

		// Alter the camera aspect radio to match the viewport.
		globalCamera->setAspectRatio(Ogre::Real(viewport->getActualWidth()) /
				Ogre::Real(viewport->getActualHeight()));

		if(shadowListener)
			shadowListener->SetCamera(globalCamera);

	}

	bool OgreRenderer::RenderFrame(float delta)
	{
		if(!root)
			return false;

		// Advance the animations.
		GeometryInstanceList::iterator it = animatedGeometry.begin();
		while(it != animatedGeometry.end())
		{
			boost::shared_ptr<GeometryInstance> geom = it->lock();
			if(geom)
			{
				// Notfy the animated geometry of the frame.
				geom->Frame(delta);
				it++;
			}
			else
			{
				// Erase obsolete geometry.
				animatedGeometry.erase(it++);
			}
		}

		if(!rendering)
			return true;

		// Pump window messages.
		Ogre::WindowEventUtilities::messagePump();
		if(renderWindow->isClosed())
			return false;

		if(!root->renderOneFrame())
			return false;

		return true;
	}

	void OgreRenderer::Shutdown()
	{
		if(!root)
			return;

		if(myGuiOgre)
		{
			myGuiOgre->shutdown();
			delete myGuiOgre;
			myGuiOgre = NULL;
		}

		// Remove ourself as a Window listener
		if(rendering)
		{
			Ogre::WindowEventUtilities::removeWindowEventListener(renderWindow, this);
			windowClosed(renderWindow);
		}

		// Destroy the root object.
		delete root;

		// Clear pointers.
		root = NULL;
		renderWindow = NULL;

		// Destroy the ogre loader.
		Engine::Get().GetResourceManager()->UnregisterResourceLoader(ogreLoader);
		ogreLoader.reset();
	}

	boost::shared_ptr<ISceneNode> OgreRenderer::CreateSceneNode()
	{
		return boost::shared_ptr<ISceneNode>
			(new SceneNode(sceneManager, sceneManager->createSceneNode()));
	}

	boost::shared_ptr<ICamera> OgreRenderer::CreateCamera(const std::string &name)
	{
		return boost::shared_ptr<ICamera>
			(new Camera(sceneManager, sceneManager->createCamera(name)));
	}

	void OgreRenderer::SetCurrentCamera(boost::shared_ptr<ICamera> camera)
	{
		// Ignore when not rendering.
		if(!rendering)
			return;

		// Cast the camera.
		boost::shared_ptr<Camera> c = boost::dynamic_pointer_cast<Camera> (camera);
		if(!c && camera)
			throw XernException("Invalid camera object.");

		// Get the ogre camera.
		Ogre::Camera *newCamera = globalCamera;
		if(c)
			newCamera = c->GetOgreCamera();

		// Remove the viewports.
		renderWindow->removeAllViewports();

		// Create a new viewport
		viewport = renderWindow->addViewport(newCamera);
		viewport->setBackgroundColour(Ogre::ColourValue(0,0,0));

		// Change the camera aspect ratio.
		newCamera->setAspectRatio(Ogre::Real(viewport->getActualWidth()) /
				Ogre::Real(viewport->getActualHeight()));

		// Store the new camera.
		currentCamera = newCamera;

		// Notify the shadow listener.
		if(shadowListener)
			shadowListener->SetCamera(currentCamera);
	}

	boost::shared_ptr<IMaterial> OgreRenderer::CreateMaterial(const std::string &baseName)
	{
		// Find the base material.
		Ogre::MaterialPtr baseMat =
				Ogre::MaterialManager::getSingleton().getByName(baseName,
				Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME);

		// Check the base material.
		if(baseMat.isNull())
			throw XernException("Couldn't find the base material.");

		// Generate the material name.
		char matName[128];
		sprintf(matName, "GenMaterial_%d", materialCounter++);

		// Clone the material.
		Ogre::MaterialPtr newMat = baseMat->clone(matName);

		// Return the material.
		boost::shared_ptr<IMaterial> ret(new Material(newMat));
		return ret;
	}

	inline Ogre::PixelFormat ConvertFormat(ITexture::TextureFormat format)
	{
		Ogre::PixelFormat pixFormat;
		switch(format)
		{
		case ITexture::TF_RGB8:
			pixFormat = Ogre::PF_R8G8B8;
			break;
		case ITexture::TF_RGBA8:
			pixFormat = Ogre::PF_R8G8B8A8;
			break;
		case ITexture::TF_R16F:
			pixFormat = Ogre::PF_FLOAT16_R;
			break;
		case ITexture::TF_RG16F:
			pixFormat = Ogre::PF_FLOAT16_GR;
			break;
		case ITexture::TF_RGB16F:
			pixFormat = Ogre::PF_FLOAT16_RGB;
			break;
		case ITexture::TF_RGBA16F:
			pixFormat = Ogre::PF_FLOAT16_RGBA;
			break;
		case ITexture::TF_R32F:
			pixFormat = Ogre::PF_FLOAT32_R;
			break;
		case ITexture::TF_RG32F:
			pixFormat = Ogre::PF_FLOAT32_GR;
			break;
		case ITexture::TF_RGB32F:
			pixFormat = Ogre::PF_FLOAT32_RGB;
			break;
		case ITexture::TF_RGBA32F:
			pixFormat = Ogre::PF_FLOAT32_RGBA;
			break;
		case ITexture::TF_L8:
			pixFormat = Ogre::PF_L8;
			break;
		case ITexture::TF_LA8:
			pixFormat = Ogre::PF_BYTE_LA;
			break;
		default:
			throw XernException("Unsupported texture format.");
			break;
		}

		return pixFormat;
	}

	boost::shared_ptr<ITexture> OgreRenderer::CreateTexture2D(int width, int height, ITexture::TextureFormat format, bool dynamic)
	{
		// Sannity check.
		assert(width > 0);
		assert(height > 0);

		// Generate the texture name.
		char texName[128];
		sprintf(texName, "GenTexture_%d", textureCounter++);

		// Convert the texture format.
		Ogre::PixelFormat pixFormat = ConvertFormat(format);

		// Create the ogre texture.
		Ogre::TexturePtr otexture =
				Ogre::TextureManager::getSingleton().createManual(
						texName,
						Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
						Ogre::TEX_TYPE_2D,
						width, height,
						0, pixFormat,
						dynamic ? Ogre::TU_DYNAMIC_WRITE_ONLY_DISCARDABLE : Ogre::TU_DEFAULT);

		// Return the texture.
		boost::shared_ptr<ITexture> ret(new Texture(otexture, format, 0, NULL));
		return ret;
	}

	boost::shared_ptr<ITexture>
	OgreRenderer::CreateTexture2D(int width, int height, ITexture::TextureFormat format, size_t dataLen, const void *data, float gammaFactor)
	{
		// Sannity check.
		assert(width > 0);
		assert(height > 0);

		// Generate the texture name.
		char texName[128];
		sprintf(texName, "GenTexture_%d", textureCounter++);

		// Create the data stream.
		Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream((void*)data, dataLen, false, true));

		// Convert the texture format.
		Ogre::PixelFormat pixFormat = ConvertFormat(format);

		// Load the texture.
		Ogre::TexturePtr otexture =
						Ogre::TextureManager::getSingleton().loadRawData(
								texName,
								Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
								stream,
								width,
								height,
								pixFormat,
								Ogre::TEX_TYPE_2D,
								Ogre::MIP_DEFAULT,
								gammaFactor,
								false);

		// Return the texture.
		boost::shared_ptr<ITexture> ret(new Texture(otexture, format, dataLen, data));
		return ret;
	}

	boost::shared_ptr<ISceneNode> OgreRenderer::CreateRegionNode()
	{
		return CreateSceneNode();
	}

	boost::shared_ptr<ISceneNode> OgreRenderer::CreateWorldNode()
	{
		return boost::shared_ptr<ISceneNode>
			(new SceneNode(sceneManager, sceneManager->getRootSceneNode()));
	}

	boost::shared_ptr<IGeometry>
	OgreRenderer::CreateGeometry(boost::shared_ptr<RegionMesh> mesh)
	{
		using namespace Ogre;

		// Create the mesh name.
		char name[32];
		char entName[32];
		sprintf(name, "RegionMesh_%d", meshCounter);
		sprintf(name, "RegionMeshEnt_%d", meshCounter++);

		// Create the mesh.
		MeshPtr omesh = MeshManager::getSingleton().createManual(name, "General");

		// Create vertex data structure.
		omesh->sharedVertexData = new Ogre::VertexData();
		omesh->sharedVertexData->vertexCount = mesh->numVertices;

		// Create declaration of vertex data.
		VertexDeclaration *decl = omesh->sharedVertexData->vertexDeclaration;
		size_t offset = 0;

		unsigned int flags = mesh->vertexFlags;
		if(flags & RMVF_POSITION)
		{
			decl->addElement(0, offset, VET_FLOAT3, VES_POSITION);
			offset += VertexElement::getTypeSize(VET_FLOAT3);
		}

		if(flags & RMVF_NORMAL)
		{
			decl->addElement(0, offset, VET_FLOAT3, VES_NORMAL);
			offset += VertexElement::getTypeSize(VET_FLOAT3);
		}

		if(flags & RMVF_TEXCOORD)
		{
			decl->addElement(0, offset, VET_FLOAT2, VES_TEXTURE_COORDINATES, 0);
			offset += VertexElement::getTypeSize(VET_FLOAT2);
		}

		if(flags & RMVF_LIGHTCOORD)
		{
			decl->addElement(0, offset, VET_FLOAT2, VES_TEXTURE_COORDINATES, 1);
			offset += VertexElement::getTypeSize(VET_FLOAT2);
		}

		if(flags & RMVF_TANGENT)
		{
			decl->addElement(0, offset, VET_FLOAT3, VES_TANGENT);
			offset += VertexElement::getTypeSize(VET_FLOAT3);
		}

		if(flags & RMVF_BITANGENT)
		{
			decl->addElement(0, offset, VET_FLOAT3, VES_BINORMAL);
			offset += VertexElement::getTypeSize(VET_FLOAT3);
		}

		// Allocate vertex buffer.
		HardwareVertexBufferSharedPtr vbuf =
				HardwareBufferManager::getSingleton().createVertexBuffer(
						decl->getVertexSize(0),
						omesh->sharedVertexData->vertexCount,
						HardwareBuffer::HBU_STATIC_WRITE_ONLY);

		// Upload the vertices into the card.
		vbuf->writeData(0, vbuf->getSizeInBytes(), mesh->vertexBuffer.get(), true);

		// Set vertex buffer binding.
		VertexBufferBinding *bind = omesh->sharedVertexData->vertexBufferBinding;
		bind->setBinding(0, vbuf);

		// Create submeshes.
		for(size_t i = 0; i < mesh->subMeshes.size(); i++)
		{
			boost::shared_ptr<RegionSubMesh> subMesh = mesh->subMeshes[i];
			if(subMesh->indexSize == 1)
				continue;

			SubMesh *osubMesh = omesh->createSubMesh();

			// Allocate index buffer.
			HardwareIndexBuffer::IndexType itype = HardwareIndexBuffer::IT_16BIT;
			switch(subMesh->indexSize)
			{
			case 2:
				itype = HardwareIndexBuffer::IT_16BIT;
				break;
			case 4:
				itype = HardwareIndexBuffer::IT_32BIT;
				break;
			}

			HardwareIndexBufferSharedPtr ibuf = HardwareBufferManager::getSingleton().
				createIndexBuffer(itype, subMesh->numTriangles*3, HardwareBuffer::HBU_STATIC_WRITE_ONLY);

			// Upload the index data.
			ibuf->writeData(0, ibuf->getSizeInBytes(), subMesh->triangles.get(), true);

			// Set parameters of the submesh.
			osubMesh->useSharedVertices = true;
			osubMesh->indexData->indexBuffer = ibuf;
			osubMesh->indexData->indexCount = subMesh->numTriangles*3;
			osubMesh->indexData->indexStart = 0;
			osubMesh->setMaterialName(subMesh->materialName);
		}

		const AABox &box = mesh->boundingBox;
		omesh->_setBounds(AxisAlignedBox(
				box.min.x, box.min.y, box.min.z,
				box.max.x, box.max.y, box.max.z));

		float radius = (box.max - box.min).Length()*0.5f;
		omesh->_setBoundingSphereRadius(radius);

		// Notify the mesh object.
		omesh->load();

		// Create the geometry interface.
		boost::shared_ptr<IGeometry> ret;
		ret.reset(new Geometry(shared_from_this(), sceneManager, omesh));

		// Return the geometry interface.
		return ret;
	}

	boost::shared_ptr<IGeometry> OgreRenderer::CreateGeometry(const std::string &meshfile, const std::string &group)
	{
		// Create the mesh.
		Ogre::MeshPtr omesh = Ogre::MeshManager::getSingleton().prepare(meshfile, group);

		// Create the geometry interface.
		boost::shared_ptr<IGeometry> ret;
		ret.reset(new Geometry(shared_from_this(), sceneManager, omesh));

		// Return the geometry interface.
		return ret;
	}

	boost::shared_ptr<IGeometry> OgreRenderer::CreateGeometry(const std::string &name, const std::string &meshfile, const std::string &group)
	{
		// Name is unused.
		return CreateGeometry(meshfile, group);
	}

	boost::shared_ptr<IGeometry> OgreRenderer::CreateTerrainPlane(float width, float height, int mapWidth, int mapHeight, Vector3 planeNormal, bool useNormals)
	{
		// Create the mesh name.
		char name[32];
		sprintf(name, "TerrainMesh_%d", meshCounter++);

		Ogre::MeshPtr omesh =
				Ogre::MeshManager::getSingleton().createManual(name, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
		// Create shared vertices.
		size_t vertexCount = mapWidth*mapHeight;
		omesh->sharedVertexData = new Ogre::VertexData();
		omesh->sharedVertexData->vertexCount = vertexCount;

		// Create vertex declaration.
		Ogre::VertexDeclaration *decl = omesh->sharedVertexData->vertexDeclaration;
		size_t offset = 0;
		int source = 0;

		// Create the height buffer.
		decl->addElement(source, offset, Ogre::VET_FLOAT1, Ogre::VES_TEXTURE_COORDINATES, 1);
		offset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT1);

		Ogre::HardwareVertexBufferSharedPtr vbuf =
				Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
						offset, omesh->sharedVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);

		float *heights = new float[vertexCount];
		memset(heights, 0, sizeof(float)*vertexCount);
		vbuf->writeData(0, vbuf->getSizeInBytes(), heights, true);
		delete [] heights;

		// Bind the height buffer.
		Ogre::VertexBufferBinding *bind = omesh->sharedVertexData->vertexBufferBinding;
		bind->setBinding(0, vbuf);

		// Create the normal buffer.
		if(useNormals)
		{
			offset = 0;
			source++;
			decl->addElement(source, offset, Ogre::VET_FLOAT3, Ogre::VES_NORMAL);
			offset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3);

			vbuf = Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
					offset, omesh->sharedVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);

			float *normals = new float[vertexCount*3];
			for(int i = 0; i < vertexCount; i++)
			{
				normals[i*3] = planeNormal.x;
				normals[i*3+1] = planeNormal.y;
				normals[i*3+2] = planeNormal.z;
			}
			vbuf->writeData(0, vbuf->getSizeInBytes(), normals, true);
			delete [] normals;

			// Bind the normal buffer.
			bind->setBinding(1, vbuf);
		}

		Vector3 s;
		Vector3 t;

		if(CompareFloat(planeNormal.x, 0.0))
		{
			s = Vector3(0.0f, 1.0f, 0.0f);
			t = Vector3(0.0f, 0.0f, 1.0f);
		}
		else if(CompareFloat(planeNormal.y, 0.0))
		{
			s = Vector3(1.0f, 0.0f, 0.0f);
			t = Vector3(0.0f, 0.0f, 1.0f);
		}
		else
		{
			s = Vector3(1.0f, 0.0f, 0.0f);
			t = Vector3(0.0f, 1.0f, 0.0f);
		}

		printf("ter s: %f %f %f\n", s.x, s.y, s.z);
		printf("ter t: %f %f %f\n", t.x, t.y, t.z);

		// Create the texture coordinate and positon buffer.
		offset = 0;
		source++;
		decl->addElement(source, offset, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES, 0);
		offset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2);
		decl->addElement(source, offset, Ogre::VET_FLOAT3, Ogre::VES_POSITION);
		offset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3);

		vbuf = Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
				offset, omesh->sharedVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);

		float *texcoords = new float[vertexCount*5];
		for(int y = 0; y < mapHeight; y++)
		{
			float *line = texcoords + y*mapWidth*5;
			for(int x = 0; x < mapWidth; x++)
			{
				float u = float(x)/float(mapWidth-1);
				float v = float(y)/float(mapHeight-1);;
				line[x*5] = u;
				line[x*5+1] = v;

				Vector3 p = u*width*s + v*height*t;
				line[x*5+2] = p.x;
				line[x*5+3] = p.y;
				line[x*5+4] = p.z;
			}
		}

		vbuf->writeData(0, vbuf->getSizeInBytes(), texcoords, true);
		delete [] texcoords;

		// Bind the texture coordinates.
		bind->setBinding(source, vbuf);

		// Create the index buffer.
		size_t numquads = (mapWidth-1)*(mapHeight-1);
		size_t numindex = numquads*6;

		Ogre::HardwareIndexBufferSharedPtr ibuf =
				Ogre::HardwareBufferManager::getSingleton().
				createIndexBuffer(
						Ogre::HardwareIndexBuffer::IT_16BIT,
						numindex,
						Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);

		// Upload the indices.
		unsigned short *indices = new unsigned short[numindex];
		for(int y = 0; y < mapHeight - 1; y++)
		{
			unsigned short *line = indices + y*(mapWidth-1)*6;
			for(int x = 0; x < mapWidth - 1; x++)
			{
				line[x*6] = y*mapWidth + x;
				line[x*6+1] = y*mapWidth + x + 1;
				line[x*6+2] = (y+1)*mapWidth + x;

				line[x*6+3] = y*mapWidth + x + 1;
				line[x*6+4] = (y+1)*mapWidth + x + 1;
				line[x*6+5] = (y+1)*mapWidth + x;
			}
		}

		ibuf->writeData(0, ibuf->getSizeInBytes(), indices, true);
		delete [] indices;

		// Bind the index buffer.
		Ogre::SubMesh *sub = omesh->createSubMesh();
		sub->useSharedVertices = true;
		sub->indexData->indexBuffer = ibuf;
		sub->indexData->indexCount = numindex;
		sub->indexData->indexStart = 0;

		// Create the geometry interface.
		boost::shared_ptr<IGeometry> ret;
		ret.reset(new Geometry(shared_from_this(), sceneManager, omesh));

	    /// Set bounding information (for culling)
	    omesh->_setBounds(Ogre::AxisAlignedBox(0,-200,0, width,200,height));
	    omesh->_setBoundingSphereRadius(Ogre::Math::Sqrt(200*200 + width*width + height*height));

	    /// Notify -Mesh object that it has been loaded
	    omesh->load();

		// Return the geometry interface.
		return ret;
	}

	boost::shared_ptr<IProceduralPrimitive> OgreRenderer::CreatePrimitive(int primWidth, int primHeight)
	{
		// Create the mesh name.
		char name[32];
		sprintf(name, "PrimitiveMesh_%d", meshCounter++);

		Ogre::MeshPtr omesh =
				Ogre::MeshManager::getSingleton().createManual(name, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
		// Create shared vertices.
		size_t vertexCount = primWidth*primWidth;
		omesh->sharedVertexData = new Ogre::VertexData();
		omesh->sharedVertexData->vertexCount = vertexCount;

		// Create vertex declaration.
		Ogre::VertexDeclaration *decl = omesh->sharedVertexData->vertexDeclaration;
		size_t offset = 0;
		int source = 0;

		// Create the position buffer.
		decl->addElement(source, offset, Ogre::VET_FLOAT3, Ogre::VES_POSITION);
		offset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3);

		Ogre::HardwareVertexBufferSharedPtr vbuf =
				Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
				offset, omesh->sharedVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);

		float *positions = new float[vertexCount*3];
		for(int y = 0; y < primHeight; y++)
		{
			float *line = positions + y*primWidth*3;
			for(int x = 0; x < primWidth; x++)
			{
				line[x*3] = float(x)/float(primWidth-1);
				line[x*3+1] = 0.0f;
				line[x*3+2] = float(y)/float(primHeight-1);
			}
		}
		vbuf->writeData(0, vbuf->getSizeInBytes(), positions, true);
		delete [] positions;

		// Bind the position buffer.
		Ogre::VertexBufferBinding *bind = omesh->sharedVertexData->vertexBufferBinding;
		bind->setBinding(source, vbuf);

		// Create the normal buffer.
		offset = 0;
		source++;
		decl->addElement(source, offset, Ogre::VET_FLOAT3, Ogre::VES_NORMAL);
		offset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3);

		vbuf = Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
				offset, omesh->sharedVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);

		float *normals = new float[vertexCount*3];
		for(int i = 0; i < vertexCount; i++)
		{
			normals[i*3] = 0.0f;
			normals[i*3+1] = 1.0f;
			normals[i*3+2] = 0.0f;
		}
		vbuf->writeData(0, vbuf->getSizeInBytes(), normals, true);
		delete [] normals;

		// Bind the normal buffer.
		bind->setBinding(source, vbuf);

		// Create the texture coordinate buffer.
		offset = 0;
		source++;
		decl->addElement(source, offset, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES, 0);
		offset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2);

		vbuf = Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
				offset, omesh->sharedVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);

		float *texcoords = new float[vertexCount*2];
		for(int y = 0; y < primHeight; y++)
		{
			float *line = texcoords + y*primWidth*2;
			for(int x = 0; x < primWidth; x++)
			{
				line[x*2] = float(x)/float(primWidth-1);;
				line[x*2+1] = float(y)/float(primHeight-1);
			}
		}

		vbuf->writeData(0, vbuf->getSizeInBytes(), texcoords, true);
		delete [] texcoords;

		// Bind the texture coordinates.
		bind->setBinding(source, vbuf);

		// Create the index buffer.
		size_t numquads = (primWidth-1)*(primHeight-1);
		size_t numindex = numquads*6;

		Ogre::HardwareIndexBufferSharedPtr ibuf =
				Ogre::HardwareBufferManager::getSingleton().
				createIndexBuffer(
						Ogre::HardwareIndexBuffer::IT_16BIT,
						numindex,
						Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);

		// Upload the indices.
		unsigned short *indices = new unsigned short[numindex];
		for(int y = 0; y < primHeight - 1; y++)
		{
			unsigned short *line = indices + y*(primWidth-1)*6;
			for(int x = 0; x < primWidth - 1; x++)
			{
				line[x*6+2] = y*primWidth + x;
				line[x*6+1] = y*primWidth + x + 1;
				line[x*6] = (y+1)*primWidth + x;

				line[x*6+5] = y*primWidth + x + 1;
				line[x*6+4] = (y+1)*primWidth + x + 1;
				line[x*6+3] = (y+1)*primWidth + x;
			}
		}

		ibuf->writeData(0, ibuf->getSizeInBytes(), indices, true);
		delete [] indices;

		// Bind the index buffer.
		Ogre::SubMesh *sub = omesh->createSubMesh();
		sub->useSharedVertices = true;
		sub->indexData->indexBuffer = ibuf;
		sub->indexData->indexCount = numindex;
		sub->indexData->indexStart = 0;

		// Create the primtive interface.
		boost::shared_ptr<IProceduralPrimitive> ret;
		ret.reset(new Primitive(shared_from_this(), sceneManager, omesh, primWidth, primHeight));

	    // Set bounding information (for culling)
	    omesh->_setBounds(Ogre::AxisAlignedBox(-1,-1,-1, 1,1,1));
	    omesh->_setBoundingSphereRadius(1);

	    // Notify -Mesh object that it has been loaded
	    omesh->load();

		// Return the geometry interface.
		return ret;
	}

	boost::shared_ptr<ILight> OgreRenderer::CreateLight(const std::string &name)
	{
		boost::shared_ptr<ILight> light;
		light.reset(new Light(sceneManager, sceneManager->createLight(name)));
		Engine::Get().GetResourceManager()->RegisterExternalResource(light);
		return light;
	}

	boost::shared_ptr<ILight> OgreRenderer::CreateLight()
	{
		boost::shared_ptr<ILight> light;
		light.reset(new Light(sceneManager, sceneManager->createLight()));
		Engine::Get().GetResourceManager()->RegisterExternalResource(light);
		return light;
	}

	void OgreRenderer::SetSkybox(boost::shared_ptr<IMaterial> material)
	{
		if(!material)
			return;

		boost::shared_ptr<Material> omat = boost::dynamic_pointer_cast<Material> (material);
		SetSkybox(omat->GetName());
	}

	void OgreRenderer::SetSkybox(const std::string &material)
	{
		sceneManager->setSkyBox(!material.empty(), material, 100);
	}

	boost::shared_ptr<IParticleSystem> OgreRenderer::CreateParticleSystem(const std::string &templateName)
	{
		// Generate the system name.
		char name[64];
		sprintf(name, "PartSystem_%d", systemCounter++);

		// Create the system.
		Ogre::ParticleSystem *system = sceneManager->createParticleSystem(name, templateName);

		// Return the particle system.
		boost::shared_ptr<IParticleSystem> ret(new ParticleSystem(system));
		Engine::Get().GetResourceManager()->RegisterExternalResource(ret);
		return ret;
	}

	std::string OgreRenderer::CreateEntityName()
	{
		char name[64];
		sprintf(name, "Entity_%d", entityCounter++);
		return name;
	}

	// Ogre::WindowEventListener
	void OgreRenderer::windowResized(Ogre::RenderWindow* rw)
	{
		// Retrieve the window size.
		unsigned int width, height, depth;
		int left, top;
		rw->getMetrics(width, height, depth, left, top);

		// Send the event into the input system.
		Engine::Get().GetInputSystem()->WindowResized(width, height);
	}

	void OgreRenderer::windowClosed(Ogre::RenderWindow* rw)
	{
		if(rw == renderWindow)
		{
			// Send the event into the input system.
			Engine::Get().GetInputSystem()->WindowClosed();
		}
	}

	// Ogre::FrameListener
	bool OgreRenderer::frameRenderingQueued(const Ogre::FrameEvent& evt)
	{
		// Check the render window.
		if(renderWindow->isClosed())
			return false;

		// Capture the input.
		Engine::Get().GetInputSystem()->GrabInput();

		return true;
	}

	void OgreRenderer::RegisterAnimated(boost::shared_ptr<GeometryInstance> animated)
	{
		animatedGeometry.push_back(animated);
	}

	void OgreRenderer::SetupResources()
	{
		//printf("SetupResources\n");
		Ogre::String filesystemType = "FileSystem";
		Ogre::String zipType = "Zip";

		// Get the resource manager.
		boost::shared_ptr<ResourceManager> resMan = Engine::Get().GetResourceManager();

		// Read all of the groups.
		const ResourceManager::GroupsType &groups = resMan->GetGroups();
		ResourceManager::GroupsType::const_iterator it = groups.begin();
		for(; it != groups.end(); it++)
		{
			boost::shared_ptr<ResourceGroup> group = it->second;
			Ogre::String groupName = group->GetName();

			// Read all of the directories.
			const ResourceGroup::Directories &directories = group->GetDirectories();
			ResourceGroup::Directories::const_iterator dit = directories.begin();
			for(; dit != directories.end(); dit++)
			{
				//printf("addResource [%s]%s\n", groupName.c_str(), dit->first.c_str());
				switch(dit->second)
				{
				case ResourceGroup::DT_FILESYSTEM:
					Ogre::ResourceGroupManager::getSingleton()
					  .addResourceLocation(dit->first, filesystemType, groupName);
					break;
				case ResourceGroup::DT_ZIP:
					Ogre::ResourceGroupManager::getSingleton()
					  .addResourceLocation(dit->first, zipType, groupName);
					break;
				default:
					// Ignore the resource.
					break;
				}
			}
		}

		// Create Cache resource group.
		Ogre::ResourceGroupManager::getSingleton()
			.addResourceLocation(".", filesystemType, "Cache");
	}

	Ogre::SceneManager *OgreRenderer::GetSceneManager()
	{
		return sceneManager;
	}
}; // namespace XernOgre
